package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"time"

	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
)

// ClientConfig 连接的配置
type ClientConfig struct {
	Host       string       //ip
	Port       int64        // 端口
	Username   string       //用户名
	Password   string       //密码
	sshClient  *ssh.Client  //ssh client
	sftpClient *sftp.Client //sftp client
	LastResult string       //最近一次运行的结果
}

// createClient
func (cliConf *ClientConfig) createClient(host string, port int64, username, password string) {
	var (
		sshClient  *ssh.Client
		sftpClient *sftp.Client
		err        error
	)
	cliConf.Host = host
	cliConf.Port = port
	cliConf.Username = username
	cliConf.Password = password
	cliConf.Port = port

	config := ssh.ClientConfig{
		User: cliConf.Username,
		Auth: []ssh.AuthMethod{ssh.Password(password)},
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
		Timeout: 15 * time.Second,
	}
	addr := fmt.Sprintf("%s:%d", cliConf.Host, cliConf.Port)

	if sshClient, err = ssh.Dial("tcp", addr, &config); err != nil {
		log.Printf("获取 sshClient 失败 %#v", err)
		failedNoExits("获取 sshClient 失败，检查sftp配置信息！！！")
	}
	cliConf.sshClient = sshClient

	// 获取sshClient，再使用sshClient构建sftpClient
	if sftpClient, err = sftp.NewClient(sshClient); err != nil {
		log.Printf("构建 sftpClient 失败 %#v", err)
		failedNoExits("构建 sftpClient 失败！！！")
	}
	cliConf.sftpClient = sftpClient
}

// RunShell 执行shell
func (cliConf *ClientConfig) RunShell(shell string) string {
	var (
		session *ssh.Session
		err     error
	)
	// 获取session，这个session是用来远程执行操作的
	if session, err = cliConf.sshClient.NewSession(); err != nil {
		log.Printf("获取远程操作 session 失败 %#v", err)
		failedNoExits("获取 session 失败，程序结束！！！")
	}
	//执行shell
	if output, err := session.CombinedOutput(shell); err != nil {
		log.Printf("执行shell=%s失败 %#v", shell, err)
	} else {
		cliConf.LastResult = string(output)
	}
	return cliConf.LastResult
}

// Upload 本地上传文件到远程
func (cliConf *ClientConfig) Upload(srcPath, dstPath string) {
	srcFile, _ := os.Open(srcPath)                   //本地
	dstFile, _ := cliConf.sftpClient.Create(dstPath) //远程
	log.Printf("**********开始上传文件")
	defer func() {
		_ = srcFile.Close()
		_ = dstFile.Close()
	}()
	buf := make([]byte, 10240)
	for {
		n, err := srcFile.Read(buf)
		if err != nil {
			if err != io.EOF {
				log.Printf("上传文件失败: %#v", err)
			} else {
				break
			}
		}
		_, err = dstFile.Write(buf[:n])
		if err != nil {
			log.Printf("sftp文件写入失败: %#v", err)
		}
	}
	log.Printf("**********结束上传文件，并去目标服务器查找文件，检查远程文件 %s**********", dstPath)
	//log.Printf("**********结束上传文件，并去目标服务器查找文件，执行命令 ls %s**********",dstPath)
	//fmt.Println(cliConf.RunShell(fmt.Sprintf("ls %s", dstPath)))
}

// Download 从远程下载文件
func (cliConf *ClientConfig) Download(srcPath, dstPath string) {
	srcFile, _ := cliConf.sftpClient.Open(srcPath) //远程
	dstFile, _ := os.Create(dstPath)               //本地
	defer func() {
		_ = srcFile.Close()
		_ = dstFile.Close()
	}()

	if _, err := srcFile.WriteTo(dstFile); err != nil {
		log.Printf("上传下载失败: %#v", err)
	}
	log.Printf("上传文件完成")
}
